﻿/*
 * This file is part of MonoStrategy.
 *
 * Copyright (C) 2010-2011 Christoph Husse
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors: 
 *      # Christoph Husse
 * 
 * Also checkout our homepage: http://monostrategy.codeplex.com/
 */

using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Diagnostics;
using MonoStrategy.RenderSystem;
using System.Xml;
using System.Xml.Serialization;
using System.Runtime.Serialization;
using System.Drawing;
using OpenTK;
using OpenTK.Graphics;

namespace MonoStrategy
{
    [ObfuscationAttribute(Feature = "renaming", ApplyToMembers = true)]
    public class Label : Control
    {
        private readonly static SortedDictionary<FontEntry, FontEntry> m_TextureFonts = new SortedDictionary<FontEntry, FontEntry>();

        private FontEntry m_Font = null;

        [XmlAttribute]
        public String Text { get; set; }
        [XmlAttribute]
        public String FontFamily { get; set; }
        [XmlAttribute]
        public float FontSize { get; set; }
        [XmlAttribute]
        public int FontIndent { get; set; }
        [XmlAttribute]
        public FontStyle FontStyle { get; set; }
        [XmlAttribute("FontColor")]
        public String FontColorString { get; set; }
        [XmlIgnore]
        public Color FontColor { get; private set; }

        public override void XMLPostProcess(XMLGUILayout inLayout)
        {
            base.XMLPostProcess(inLayout);

            if (String.IsNullOrEmpty(FontColorString))
                throw new ArgumentException("No font color specified.");

            if (String.IsNullOrEmpty(FontFamily))
                throw new ArgumentException("No font family specified.");

            System.Drawing.FontFamily fontFamily;

            try
            {
                fontFamily = new System.Drawing.FontFamily(FontFamily);
            }
            catch
            {
                throw new ArgumentException("Font family \"" + FontFamily + "\" is unknown.");
            }

            FontColor = Color.FromName(FontColorString);

            // create texture font
            FontEntry fontEntry = new FontEntry(FontFamily, FontSize, FontStyle, FontColor);

            lock (m_TextureFonts)
            {
                if (!m_TextureFonts.ContainsKey(fontEntry))
                {
                    m_Font = fontEntry;

                    m_Font.CreateTextureFont();
                    m_TextureFonts.Add(m_Font, m_Font);
                }
                else
                {
                    m_Font = m_TextureFonts[fontEntry];
                }
            }
        }

        public Label()
            : base()
        {
            var families = System.Drawing.FontFamily.Families;

            FontColorString = "White";
            FontStyle = System.Drawing.FontStyle.Regular;
            FontSize = 12;
            FontIndent = -3;
            FontFamily = "Arial";
        }

        private class FontEntry : IComparable<FontEntry>, IComparer<FontEntry>
        {
            private readonly SortedList<Char, Glyph> m_Glyphs = new SortedList<char, Glyph>();

            public String Family { get; private set; }
            public float Size { get; private set; }
            public FontStyle Style { get; private set; }
            public Color Color { get; private set; }
            public Font Font { get; private set; }
            public int ImageID { get; private set; }

            public class Glyph
            {
                public Char Char { get; private set; }
                public Size Size { get; private set; }
                public Rectangle TexFrame { get; set; }

                public Glyph(Char inChar, Font inFont)
                {
                    Char = inChar;
                    Size = System.Windows.Forms.TextRenderer.MeasureText(new String(inChar, 1), inFont);
                }
            }

            private FontEntry() { }

            public FontEntry(String inFamily, float inSize, FontStyle inStyle, Color inColor)
            {
                Family = inFamily;
                Size = inSize;
                Style = inStyle;
                Color = inColor;
                Font = new Font(Family, Size, Style, GraphicsUnit.Pixel);
            }

            public Glyph GetGlyph(Char inChar)
            {
                return m_Glyphs[inChar];
            }

            public void CreateTextureFont()
            {
                String availableGlyphs = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!\"§$%&/()=?{[]}\\*+~'#-_.:,;<>|ßäöüÜÖÄ@€ ";

                // prepare and measure glyphs
                int maxHeight = 0, maxWidth = 0;

                for (int i = 0; i < availableGlyphs.Length; i++)
                {
                    Glyph glyph;

                    m_Glyphs.Add(availableGlyphs[i], glyph = new Glyph(availableGlyphs[i], Font));

                    maxHeight = Math.Max(maxHeight, glyph.Size.Height);
                    maxWidth = Math.Max(maxWidth, glyph.Size.Width);
                }

                // render glyphs to texture and record their atlas coordinates
                int glyphsPerLine = (int)Math.Ceiling(Math.Sqrt(availableGlyphs.Length));
                Bitmap fontImage = new Bitmap(glyphsPerLine * maxWidth, glyphsPerLine * maxHeight, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
                Graphics graphics = Graphics.FromImage(fontImage);

                for (int y = 0, offset = 0; y < glyphsPerLine; y++)
                {
                    for (int x = 0; x < glyphsPerLine; x++)
                    {
                        if (offset >= m_Glyphs.Count)
                            break;

                        var glyph = m_Glyphs.Values[offset++];

                        glyph.TexFrame = new Rectangle(
                            x * maxWidth, 
                            y * maxHeight,
                            glyph.Size.Width,
                            glyph.Size.Height);

                        graphics.DrawString(glyph.Char.ToString(), Font, new SolidBrush(Color), x * maxWidth, y * maxHeight);
                    }
                }

                ImageID = Program.GUIConfig.RegisterImage(fontImage);
            }

            public int Compare(FontEntry x, FontEntry y)
            {
                if (x.Size < y.Size)
                    return -1;

                if (x.Size > y.Size)
                    return 1;

                if (x.Style > y.Style)
                    return 1;

                if (x.Style < y.Style)
                    return -1;

                int res;

                if ((res = x.Family.CompareTo(y.Family)) != 0)
                    return res;

                if (x.Color.ToArgb() > y.Color.ToArgb())
                    return 1;

                if (x.Color.ToArgb() < y.Color.ToArgb())
                    return -1;

                return 0;
            }

            public int CompareTo(FontEntry other)
            {
                return Compare(this, other);
            }
        }

        public Point MeasureSize(String inText)
        {
            if (!String.IsNullOrEmpty(Text))
            {
                int width = 0, height = 0;

                for (int i = 0; i < Text.Length; i++)
                {
                    var glyph = m_Font.GetGlyph(Text[i]);

                    width += glyph.Size.Width + FontIndent;
                    height = Math.Max(height, glyph.Size.Height);
                }

                return new Point(width, height);
            }
            else
                return new Point(0, 0);
        }

        protected override void Render(int inChainLeft, int inChainTop)
        {
            if (!String.IsNullOrEmpty(Text))
            {
                for (int i = 0, left = Left; i < Text.Length; i++)
                {
                    var glyph = m_Font.GetGlyph(Text[i]);

                    Renderer.DrawSpriteAtlas(
                             (inChainLeft + left) * WidthScale,
                             (inChainTop + Top) * HeightScale,
                             glyph.Size.Width * WidthScale,
                             glyph.Size.Height * HeightScale,
                             m_Font.ImageID,
                             Opacity,
                             glyph.TexFrame.X,
                             glyph.TexFrame.Y,
                             glyph.TexFrame.Width,
                             glyph.TexFrame.Height);

                    left += glyph.Size.Width + FontIndent;
                }
            }

            base.Render(inChainLeft + Left, inChainTop + Top);
        }
    }
}
